package org.jetlinks.pro.assets.impl;

import lombok.AllArgsConstructor;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.jetlinks.pro.assets.*;
import org.jetlinks.pro.assets.impl.entity.DimensionAssetBindingEntity;
import org.reactivestreams.Publisher;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Nonnull;
import java.util.Collection;

@AllArgsConstructor
@Component
public class DimensionAssetBindManager implements AssetBindManager {

    private final ReactiveRepository<DimensionAssetBindingEntity, String> repository;

    @Nonnull
    @Override
    public Mono<Integer> unbindAllAssets(@Nonnull String assetType, @Nonnull Collection<?> assetId) {
        Assert.hasText(assetType, "validation.asset_type_can_not_be_empty");
        Assert.notEmpty(assetId, "validation.asset_id_can_not_be_empty");

        return repository
            .createDelete()
            .where(DimensionAssetBindingEntity::getAssetType, assetType)
            .in(DimensionAssetBindingEntity::getAssetId, assetType)
            .execute();
    }

    @Nonnull
    @Override
    public Mono<Void> bindAssets(@Nonnull Publisher<AssetBindRequest> request) {
        return Flux
            .from(request)
            .flatMapIterable(DimensionAssetBindingEntity::of)
            .distinct(DimensionAssetBindingEntity::getId)
            .as(repository::save)
            .then();
    }

    @Nonnull
    @Override
    public Mono<Void> unbindAssets(@Nonnull Publisher<AssetUnbindRequest> requestFlux) {

        return Flux
            .from(requestFlux)
            .flatMap(request -> Flux
                .fromIterable(request.getAssetIdList())
                .map(assetId -> DimensionAssetBindingEntity
                    .generateId(request.getTargetType(),
                                request.getTargetId(),
                                request.getAssetType(),
                                assetId)))
            .as(repository::deleteById)
            .then();
    }

    @Override
    public Mono<Void> changePermission(@Nonnull String targetType,
                                       @Nonnull Collection<String> targetIdList,
                                       @Nonnull AssetType assetType,
                                       @Nonnull Collection<?> assetIdList,
                                       @Nonnull AssetPermission... permissions) {
        return Flux
            .fromIterable(targetIdList)
            .map(targetId -> DimensionAssetBindingEntity.generateKey(targetType, targetId))
            .collectList()
            .flatMap(targetKeys -> repository
                .createUpdate()
                .where()
                .set(DimensionAssetBindingEntity::getPermission, assetType.createPermission(permissions))
                .set(DimensionAssetBindingEntity::getUpdateTime, System.currentTimeMillis())
                .in(DimensionAssetBindingEntity::getTargetKey, targetKeys)
                .and(DimensionAssetBindingEntity::getAssetType, assetType.getId())
                .in(DimensionAssetBindingEntity::getAssetId, assetIdList)
                .execute())
            .then();
    }

    @Nonnull
    @Override
    public Flux<AssetBinding> getBindings(@Nonnull String targetType,
                                          @Nonnull Collection<String> targetIdList,
                                          @Nonnull AssetType assetType,
                                          @Nonnull Collection<?> assetIdList) {

        return Flux
            .fromIterable(targetIdList)
            .map(targetId -> DimensionAssetBindingEntity.generateKey(targetType, targetId))
            .collectList()
            .flatMapMany(targetKeys -> repository
                .createQuery()
                .where()
                .in(DimensionAssetBindingEntity::getTargetKey, targetKeys)
                .and(DimensionAssetBindingEntity::getAssetType, assetType.getId())
                .in(DimensionAssetBindingEntity::getAssetId, assetIdList)
                .fetch())
            .map(DimensionAssetBindingEntity::toBinding);

    }

    @Nonnull
    @Override
    public Flux<AssetBinding> getBindings(@Nonnull Collection<String> targetTypes,
                                          @Nonnull AssetType assetType,
                                          @Nonnull Collection<?> assetIdList) {
        return repository
            .createQuery()
            .where()
            .in(DimensionAssetBindingEntity::getTargetType, targetTypes)
            .and(DimensionAssetBindingEntity::getAssetType, assetType.getId())
            .in(DimensionAssetBindingEntity::getAssetId, assetIdList)
            .fetch()
            .map(DimensionAssetBindingEntity::toBinding);
    }

    @Nonnull
    @Override
    public Flux<AssetBinding> getBindings(@Nonnull String targetType,
                                          @Nonnull Collection<String> targetIdList,
                                          @Nonnull AssetType assetType) {
        Assert.notNull(assetType, "validation.asset_type_can_not_be_empty");
        Assert.notNull(targetType, "validation.target_type_can_not_be_empty");
        Assert.notEmpty(targetIdList, "validation.target_id_can_not_be_empty");

        return Flux
            .fromIterable(targetIdList)
            .map(targetId -> DimensionAssetBindingEntity.generateKey(targetType, targetId))
            .collectList()
            .flatMapMany(targetKeys -> repository
                .createQuery()
                .where()
                .in(DimensionAssetBindingEntity::getTargetKey, targetKeys)
                .and(DimensionAssetBindingEntity::getAssetType, assetType.getId())
                .fetch())
            .map(DimensionAssetBindingEntity::toBinding);

    }

    @Nonnull
    @Override
    public Flux<AssetBinding> getBindings(@Nonnull AssetType assetType,
                                          @Nonnull Collection<?> assetIdList) {
        Assert.notNull(assetType, "validation.asset_type_can_not_be_empty");
        Assert.notEmpty(assetIdList, "validation.asset_id_can_not_be_empty");

        return repository
            .createQuery()
            .where()
            .and(DimensionAssetBindingEntity::getAssetType, assetType.getId())
            .in(DimensionAssetBindingEntity::getAssetId, assetIdList)
            .fetch()
            .map(DimensionAssetBindingEntity::toBinding);
    }
}
